SPORE.$Import('core.browser');
SPORE.$Import('core.slick.finder');

/*
---

name: Element

description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.

license: MIT-style license.

requires: [Window, Document, Array, String, Function, Number, Slick.Parser, Slick.Finder]

provides: [Element, Elements, $, $$, Iframe, Selectors]

...
*/

SPORE.register('core.element.base',function($ns){

	var Element = function(tag, props){
		var konstructor = Element.Constructors[tag];
		if (konstructor) return konstructor(props);
		if (typeof tag != 'string') return document.id(tag).set(props);
	
		if (!props) props = {};
	
		if (!(/^[\w-]+$/).test(tag)){
			var parsed = Slick.parse(tag).expressions[0][0];
			tag = (parsed.tag == '*') ? 'div' : parsed.tag;
			if (parsed.id && props.id == null) props.id = parsed.id;
	
			var attributes = parsed.attributes;
			if (attributes) for (var i = 0, l = attributes.length; i < l; i++){
				var attr = attributes[i];
				if (props[attr.key] != null) continue;
	
				if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
				else if (!attr.value && !attr.operator) props[attr.key] = true;
			}
	
			if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
		}
	
		return document.newElement(tag, props);
	};
	
	if (Browser.Element) Element.prototype = Browser.Element.prototype;
	
	new Type('Element', Element).mirror(function(name){
		if (Array.prototype[name]) return;
	
		var obj = {};
		obj[name] = function(){
			var results = [], args = arguments, elements = true;
			for (var i = 0, l = this.length; i < l; i++){
				var element = this[i], result = results[i] = element[name].apply(element, args);
				elements = (elements && typeOf(result) == 'element');
			}
			return (elements) ? new Elements(results) : results;
		};
	
		Elements.implement(obj);
	});
	
	if (!Browser.Element){
		Element.parent = Object;
	
		Element.Prototype = {'$family': Function.from('element').hide()};
	
		Element.mirror(function(name, method){
			Element.Prototype[name] = method;
		});
	}
	
	Element.Constructors = {};
	
	
	
	var IFrame = new Type('IFrame', function(){
		var params = Array.link(arguments, {
			properties: Type.isObject,
			iframe: function(obj){
				return (obj != null);
			}
		});
	
		var props = params.properties || {}, iframe;
		if (params.iframe) iframe = document.id(params.iframe);
		var onload = props.onload || function(){};
		delete props.onload;
		props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
		iframe = new Element(iframe || 'iframe', props);
	
		var onLoad = function(){
			onload.call(iframe.contentWindow);
		};
	
		if (window.frames[props.id]) onLoad();
		else iframe.addListener('load', onLoad);
		return iframe;
	});
	
	var Elements = this.Elements = function(nodes){
		if (nodes && nodes.length){
			var uniques = {}, node;
			for (var i = 0; node = nodes[i++];){
				var uid = Slick.uidOf(node);
				if (!uniques[uid]){
					uniques[uid] = true;
					this.push(node);
				}
			}
		}
	};
	
	Elements.prototype = {length: 0};
	Elements.parent = Array;
	
	new Type('Elements', Elements).implement({
	
		filter: function(filter, bind){
			if (!filter) return this;
			return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
				return item.match(filter);
			} : filter, bind));
		}.protect(),
	
		push: function(){
			var length = this.length;
			for (var i = 0, l = arguments.length; i < l; i++){
				var item = document.id(arguments[i]);
				if (item) this[length++] = item;
			}
			return (this.length = length);
		}.protect(),
	
		unshift: function(){
			var items = [];
			for (var i = 0, l = arguments.length; i < l; i++){
				var item = document.id(arguments[i]);
				if (item) items.push(item);
			}
			return Array.prototype.unshift.apply(this, items);
		}.protect(),
	
		concat: function(){
			var newElements = new Elements(this);
			for (var i = 0, l = arguments.length; i < l; i++){
				var item = arguments[i];
				if (Type.isEnumerable(item)) newElements.append(item);
				else newElements.push(item);
			}
			return newElements;
		}.protect(),
	
		append: function(collection){
			for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
			return this;
		}.protect(),
	
		empty: function(){
			while (this.length) delete this[--this.length];
			return this;
		}.protect()
	
	});
	
	
	
	(function(){
	
	// FF, IE
	var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
	
	splice.call(object, 1, 1);
	if (object[1] == 1) Elements.implement('splice', function(){
		var length = this.length;
		splice.apply(this, arguments);
		while (length >= this.length) delete this[length--];
		return this;
	}.protect());
	
	Elements.implement(Array.prototype);
	
	Array.mirror(Elements);
	
	/*<ltIE8>*/
	var createElementAcceptsHTML;
	try {
		var x = document.createElement('<input name=x>');
		createElementAcceptsHTML = (x.name == 'x');
	} catch(e){}
	
	var escapeQuotes = function(html){
		return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
	};
	/*</ltIE8>*/
	
	Document.implement({
	
		newElement: function(tag, props){
			if (props && props.checked != null) props.defaultChecked = props.checked;
			/*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
			if (createElementAcceptsHTML && props){
				tag = '<' + tag;
				if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
				if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
				tag += '>';
				delete props.name;
				delete props.type;
			}
			/*</ltIE8>*/
			return this.id(this.createElement(tag)).set(props);
		}
	
	});
	
	})();
	
	Document.implement({
	
		newTextNode: function(text){
			return this.createTextNode(text);
		},
	
		getDocument: function(){
			return this;
		},
	
		getWindow: function(){
			return this.window;
		},
	
		id: (function(){
	
			var types = {
	
				string: function(id, nocash, doc){
					id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
					return (id) ? types.element(id, nocash) : null;
				},
	
				element: function(el, nocash){
					$uid(el);
					if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
						Object.append(el, Element.Prototype);
					}
					return el;
				},
	
				object: function(obj, nocash, doc){
					if (obj.toElement) return types.element(obj.toElement(doc), nocash);
					return null;
				}
	
			};
	
			types.textnode = types.whitespace = types.window = types.document = function(zero){
				return zero;
			};
	
			return function(el, nocash, doc){
				if (el && el.$family && el.uid) return el;
				var type = typeOf(el);
				return (types[type]) ? types[type](el, nocash, doc || document) : null;
			};
	
		})()
	
	});
	
	if (window.$ == null) Window.implement('$', function(el, nc){
		return document.id(el, nc, this.document);
	});
	
	Window.implement({
	
		getDocument: function(){
			return this.document;
		},
	
		getWindow: function(){
			return this;
		}
	
	});
	
	[Document, Element].invoke('implement', {
	
		getElements: function(expression){
			return Slick.search(this, expression, new Elements);
		},
	
		getElement: function(expression){
			return document.id(Slick.find(this, expression));
		}
	
	});
	
	
	
	if (window.$$ == null) Window.implement('$$', function(selector){
		if (arguments.length == 1){
			if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
			else if (Type.isEnumerable(selector)) return new Elements(selector);
		}
		return new Elements(arguments);
	});
	
	(function(){
	
	var collected = {}, storage = {};
	var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
	
	var get = function(uid){
		return (storage[uid] || (storage[uid] = {}));
	};
	
	var clean = function(item){
		var uid = item.uid;
		if (item.removeEvents) item.removeEvents();
		if (item.clearAttributes) item.clearAttributes();
		if (uid != null){
			delete collected[uid];
			delete storage[uid];
		}
		return item;
	};
	
	var camels = ['defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly',
		'rowSpan', 'tabIndex', 'useMap'
	];
	var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readOnly', 'multiple', 'selected',
		'noresize', 'defer', 'defaultChecked'
	];
	 var attributes = {
		'html': 'innerHTML',
		'class': 'className',
		'for': 'htmlFor',
		'text': (function(){
			var temp = document.createElement('div');
			return (temp.textContent == null) ? 'innerText' : 'textContent';
		})()
	};
	var readOnly = ['type'];
	var expandos = ['value', 'defaultValue'];
	var uriAttrs = /^(?:href|src|usemap)$/i;
	
	bools = bools.associate(bools);
	camels = camels.associate(camels.map(String.toLowerCase));
	readOnly = readOnly.associate(readOnly);
	
	Object.append(attributes, expandos.associate(expandos));
	
	var inserters = {
	
		before: function(context, element){
			var parent = element.parentNode;
			if (parent) parent.insertBefore(context, element);
		},
	
		after: function(context, element){
			var parent = element.parentNode;
			if (parent) parent.insertBefore(context, element.nextSibling);
		},
	
		bottom: function(context, element){
			element.appendChild(context);
		},
	
		top: function(context, element){
			element.insertBefore(context, element.firstChild);
		}
	
	};
	
	inserters.inside = inserters.bottom;
	
	
	
	var injectCombinator = function(expression, combinator){
		if (!expression) return combinator;
	
		expression = Object.clone(Slick.parse(expression));
	
		var expressions = expression.expressions;
		for (var i = expressions.length; i--;)
			expressions[i][0].combinator = combinator;
	
		return expression;
	};
	
	Element.implement({
	
		set: function(prop, value){
			var property = Element.Properties[prop];
			(property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
		}.overloadSetter(),
	
		get: function(prop){
			var property = Element.Properties[prop];
			return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
		}.overloadGetter(),
	
		erase: function(prop){
			var property = Element.Properties[prop];
			(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
			return this;
		},
	
		setProperty: function(attribute, value){
			attribute = camels[attribute] || attribute;
			if (value == null) return this.removeProperty(attribute);
			var key = attributes[attribute];
			(key) ? this[key] = value :
				(bools[attribute]) ? this[attribute] = !!value : this.setAttribute(attribute, '' + value);
			return this;
		},
	
		setProperties: function(attributes){
			for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
			return this;
		},
	
		getProperty: function(attribute){
			attribute = camels[attribute] || attribute;
			var key = attributes[attribute] || readOnly[attribute];
			return (key) ? this[key] :
				(bools[attribute]) ? !!this[attribute] :
				(uriAttrs.test(attribute) ? this.getAttribute(attribute, 2) :
				(key = this.getAttributeNode(attribute)) ? key.nodeValue : null) || null;
		},
	
		getProperties: function(){
			var args = Array.from(arguments);
			return args.map(this.getProperty, this).associate(args);
		},
	
		removeProperty: function(attribute){
			attribute = camels[attribute] || attribute;
			var key = attributes[attribute];
			(key) ? this[key] = '' :
				(bools[attribute]) ? this[attribute] = false : this.removeAttribute(attribute);
			return this;
		},
	
		removeProperties: function(){
			Array.each(arguments, this.removeProperty, this);
			return this;
		},
	
		hasClass: function(className){
			return this.className.clean().contains(className, ' ');
		},
	
		addClass: function(className){
			if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
			return this;
		},
	
		removeClass: function(className){
			this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
			return this;
		},
	
		toggleClass: function(className, force){
			if (force == null) force = !this.hasClass(className);
			return (force) ? this.addClass(className) : this.removeClass(className);
		},
	
		adopt: function(){
			var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
			if (length > 1) parent = fragment = document.createDocumentFragment();
	
			for (var i = 0; i < length; i++){
				var element = document.id(elements[i], true);
				if (element) parent.appendChild(element);
			}
	
			if (fragment) this.appendChild(fragment);
	
			return this;
		},
	
		appendText: function(text, where){
			return this.grab(this.getDocument().newTextNode(text), where);
		},
	
		grab: function(el, where){
			inserters[where || 'bottom'](document.id(el, true), this);
			return this;
		},
	
		inject: function(el, where){
			inserters[where || 'bottom'](this, document.id(el, true));
			return this;
		},
	
		replaces: function(el){
			el = document.id(el, true);
			el.parentNode.replaceChild(this, el);
			return this;
		},
	
		wraps: function(el, where){
			el = document.id(el, true);
			return this.replaces(el).grab(el, where);
		},
	
		getPrevious: function(expression){
			return document.id(Slick.find(this, injectCombinator(expression, '!~')));
		},
	
		getAllPrevious: function(expression){
			return Slick.search(this, injectCombinator(expression, '!~'), new Elements);
		},
	
		getNext: function(expression){
			return document.id(Slick.find(this, injectCombinator(expression, '~')));
		},
	
		getAllNext: function(expression){
			return Slick.search(this, injectCombinator(expression, '~'), new Elements);
		},
	
		getFirst: function(expression){
			return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
		},
	
		getLast: function(expression){
			return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
		},
	
		getParent: function(expression){
			return document.id(Slick.find(this, injectCombinator(expression, '!')));
		},
	
		getParents: function(expression){
			return Slick.search(this, injectCombinator(expression, '!'), new Elements);
		},
	
		getSiblings: function(expression){
			return Slick.search(this, injectCombinator(expression, '~~'), new Elements);
		},
	
		getChildren: function(expression){
			return Slick.search(this, injectCombinator(expression, '>'), new Elements);
		},
	
		getWindow: function(){
			return this.ownerDocument.window;
		},
	
		getDocument: function(){
			return this.ownerDocument;
		},
	
		getElementById: function(id){
			return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
		},
	
		getSelected: function(){
			this.selectedIndex; // Safari 3.2.1
			return new Elements(Array.from(this.options).filter(function(option){
				return option.selected;
			}));
		},
	
		toQueryString: function(){
			var queryString = [];
			this.getElements('input, select, textarea').each(function(el){
				var type = el.type;
				if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
	
				var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
					// IE
					return document.id(opt).get('value');
				}) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
	
				Array.from(value).each(function(val){
					if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
				});
			});
			return queryString.join('&');
		},
	
		destroy: function(){
			var children = clean(this).getElementsByTagName('*');
			Array.each(children, clean);
			Element.dispose(this);
			return null;
		},
	
		empty: function(){
			Array.from(this.childNodes).each(Element.dispose);
			return this;
		},
	
		dispose: function(){
			return (this.parentNode) ? this.parentNode.removeChild(this) : this;
		},
	
		match: function(expression){
			return !expression || Slick.match(this, expression);
		}
	
	});
	
	var cleanClone = function(node, element, keepid){
		if (!keepid) node.setAttributeNode(document.createAttribute('id'));
		if (node.clearAttributes){
			node.clearAttributes();
			node.mergeAttributes(element);
			node.removeAttribute('uid');
			if (node.options){
				var no = node.options, eo = element.options;
				for (var i = no.length; i--;) no[i].selected = eo[i].selected;
			}
		}
	
		var prop = formProps[element.tagName.toLowerCase()];
		if (prop && element[prop]) node[prop] = element[prop];
	};
	
	Element.implement('clone', function(contents, keepid){
		contents = contents !== false;
		var clone = this.cloneNode(contents), i;
	
		if (contents){
			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
			for (i = ce.length; i--;) cleanClone(ce[i], te[i], keepid);
		}
	
		cleanClone(clone, this, keepid);
	
		if (Browser.ie){
			var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
			for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
		}
		return document.id(clone);
	});
	
	var contains = {contains: function(element){
		return Slick.contains(this, element);
	}};
	
	if (!document.contains) Document.implement(contains);
	if (!document.createElement('div').contains) Element.implement(contains);
	
	
	
	[Element, Window, Document].invoke('implement', {
	
		addListener: function(type, fn){
			if (type == 'unload'){
				var old = fn, self = this;
				fn = function(){
					self.removeListener('unload', fn);
					old();
				};
			} else {
				collected[$uid(this)] = this;
			}
			if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
			else this.attachEvent('on' + type, fn);
			return this;
		},
	
		removeListener: function(type, fn){
			if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
			else this.detachEvent('on' + type, fn);
			return this;
		},
	
		retrieve: function(property, dflt){
			var storage = get($uid(this)), prop = storage[property];
			if (dflt != null && prop == null) prop = storage[property] = dflt;
			return prop != null ? prop : null;
		},
	
		store: function(property, value){
			var storage = get($uid(this));
			storage[property] = value;
			return this;
		},
	
		eliminate: function(property){
			var storage = get($uid(this));
			delete storage[property];
			return this;
		}
	
	});
	
	/*<ltIE9>*/
	if (window.attachEvent && !window.addEventListener) window.addListener('unload', function(){
		Object.each(collected, clean);
		if (window.CollectGarbage) CollectGarbage();
	});
	/*</ltIE9>*/
	
	})();
	
	Element.Properties = {};
	
	
	
	Element.Properties.style = {
	
		set: function(style){
			this.style.cssText = style;
		},
	
		get: function(){
			return this.style.cssText;
		},
	
		erase: function(){
			this.style.cssText = '';
		}
	
	};
	
	Element.Properties.tag = {
	
		get: function(){
			return this.tagName.toLowerCase();
		}
	
	};
	
	/*<ltIE9>*/
	(function(maxLength){
		if (maxLength != null) Element.Properties.maxlength = Element.Properties.maxLength = {
			get: function(){
				var maxlength = this.getAttribute('maxLength');
				return maxlength == maxLength ? null : maxlength;
			}
		};
	})(document.createElement('input').getAttribute('maxLength'));
	/*</ltIE9>*/
	
	/*<!webkit>*/
	Element.Properties.html = (function(){
	
		var tableTest = Function.attempt(function(){
			var table = document.createElement('table');
			table.innerHTML = '<tr><td></td></tr>';
		});
	
		var wrapper = document.createElement('div');
	
		var translations = {
			table: [1, '<table>', '</table>'],
			select: [1, '<select>', '</select>'],
			tbody: [2, '<table><tbody>', '</tbody></table>'],
			tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
		};
		translations.thead = translations.tfoot = translations.tbody;
	
		var html = {
			set: function(){
				var html = Array.flatten(arguments).join('');
				var wrap = (!tableTest && translations[this.get('tag')]);
				if (wrap){
					var first = wrapper;
					first.innerHTML = wrap[1] + html + wrap[2];
					for (var i = wrap[0]; i--;) first = first.firstChild;
					this.empty().adopt(first.childNodes);
				} else {
					this.innerHTML = html;
				}
			}
		};
	
		html.erase = html.set;
	
		return html;
	})();
	/*</!webkit>*/
	
	this.Element = Element;
	this.IFrame = IFrame;

});